package org.gnomus.server;

import com.google.gson.Gson;
import org.gnomus.util.*;

import javax.servlet.RequestDispatcher;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

import static org.gnomus.util.strings.simplify;

public class Dispatcher {

    private static final Set<Class> controllers = classes.getAllClasses(Controller.class);
    private static final Map<String, Map<String,Method>> c2m = methods.collectMethods(controllers);

    public static boolean dispatch() {
        Context ctx = Context.get();
        String uri = ctx.uri;
        while (uri.startsWith("/")) {
            uri = uri.substring(1);
        }
        setView(ctx, uri);
        if (isIndex(uri)) {
            render(ctx, uri);
            return true;
        } else {
            if (invokeMethod(uri) && !ctx.ajax) {
                if ("access.log".equals(uri)) {
                    return false;
                }
                render(ctx, uri);
            }
        }
        return true;
    }

    private static void setView(Context ctx, String uri) {
        String view = "not_found";
        if (isIndex(uri)) {
            view = "index";
        } else {
            String[] t = uri.split("/");
            for (String s : t) {
                if (!strings.isEmpty(s)) {
                    view = s;
                    break;
                }
            }
        }
        if (io.exists(new File("WEB-INF/" + view + "/index.jsp"))) {
            ctx.view = view;
        }
    }

    private static boolean invokeMethod(String uri) {
        int ix = uri.indexOf('/');
        if (ix < 0) {
            ix = uri.indexOf('.');
        }
        String cn = null;
        String mn = null;
        if (ix > 0 && ix + 1 != uri.length()) {
            cn = uri.substring(0, ix); //controller name
            mn = uri.substring(ix + 1); // action name
        } else {
            cn = !uri.endsWith("/") ? uri : uri.substring(0, uri.length() - 1);
            mn = "index";
        }
        if ("class".equals(simplify(mn))) {
            mn = "klass";
        }
        Map<String, Method> a2m = c2m.get(simplify(cn));
        if (a2m != null) {
            Method m = a2m.get(simplify(mn));
            if (m != null) {
                Context.get().action = mn;
                return invokeMethod(m);
            }
        } else if (cn.endsWith("s")) {
            a2m = c2m.get(simplify(cn.substring(0, cn.length() - 1)));
            if (a2m != null) {
                Method m = a2m.get("list");
                if (m != null) {
                    return invokeMethod(m);
                }
            }
        }
        return false;
    }

    private static boolean invokeMethod(Method m) {
        Context ctx = Context.get();
        Object ci = classes.newInstance(m.getDeclaringClass());
        Map<String, String> params = ctx.params;
        Map<String, Field> fields = classes.getFields(ci);
        for (Map.Entry<String, Field> e: fields.entrySet()) {
            String name = e.getKey().toLowerCase();
            String value = params.get(name);
            if (value != null) {
                Field f = e.getValue();
                if (f.getType().equals(String.class)) {
                    classes.setField(ci, f, value);
                } else if (f.getType().equals(Integer.class)) {
                    classes.setField(ci, f, Integer.parseInt(value));
                }
            }
        }
        try {
            Object o = m.invoke(ci);
            return o instanceof Boolean ? (Boolean)o : false;
        } catch (Throwable t) {
            throw new Error(t);
        }
    }

    private static boolean render(Context ctx, String uri) {
        if (ctx.json != null) {
            try {
                ctx.res.setContentType("application/json");
                ctx.res.getWriter().write(new Gson().toJson(ctx.json));
                return true;
            } catch (IOException e) {
                throw new Error(e);
            }
        }
/*
        if (ctx.view == null || !io.exists(new File("WEB-INF/" + ctx.view + "/index.jsp"))) {
            return false;
        }
*/
        RequestDispatcher rd = ctx.req.getRequestDispatcher("/WEB-INF/layout/page.jsp");
        try {
            rd.include(ctx.req, ctx.res);
            return true;
        } catch (Throwable t) {
            throw new Error(t);
        }
    }

    private static boolean isIndex(String uri) {
        return strings.isEmpty(uri) || "/".equals(uri) || "/index.html".equals(uri);
    }
}
